/*
 * Copyright (C) 2009 Kristopher T Babic
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of
 * the GNU General Public License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this program;
 * if not, see <http://www.gnu.org/licenses>.
 */

package edu.idp.client.draw;

import gjt.Util;
import edu.idp.client.advrubberband.Rubberband;
import edu.idp.client.advrubberband.RubberbandEllipse;
import edu.idp.client.advrubberband.RubberbandLine;
import edu.idp.client.advrubberband.RubberbandRectangle;
import edu.idp.client.interfaces.Drawable;
import edu.idp.client.interfaces.Fillable;
import edu.idp.client.interfaces.Sizeable;
import edu.idp.client.interfaces.Storable;
import edu.idp.shared.StoredObject;
import edu.idp.shared.net.NetTransferObject;

import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.LinkedList;

/**
 * An extension of idp.client.draw.AllPurposeDrawPanel which serves
 * as a Basic Object and Display Manager.  It controls the
 * display of drawing objects through a double-buffering
 * technique, maintains the storage of all displayable objects and
 * manages the drawing of basic shapes.
 *
 * @author Kristopher T Babic
 */
abstract public class AdvancedDrawPanel extends AllPurposeDrawPanel {

    /**
     * stores the current drawing tool
     */
    protected Drawable curDraw;
    /**
     * stores a saved value of the last used drawing tool
     */
    protected Drawable savedState = null;
    /**
     * stores a saved value of the last used color
     */
    protected Color savedColor = null;
    /**
     * maintains the list of all locally drawn objects
     */
    protected LinkedList objList = null;
    /**
     * maintaiuns the list of all externally drawn objects
     */
    protected LinkedList objListExternal = null;

    // drawing tools
    private Rubberband rbLine, rbRect, rbEllipse;
    private Drawing drwPencil, drwAirBrush;

    // current color
    private Color color = Color.black;
    // boolean used to specify if drawing objects will be filled or not
    private boolean fill;
    // boolean used to specify if the drawing objects will drawn a line around the shape
    private boolean line;
    // boolean specifying whether or not the sreen needs to be displayed
    private boolean compRedraw = false;
    // off-screen buffer
    private OffscreenBuffer imageBuffer = new OffscreenBuffer();

    /**
     * Initializes all basic drawing objects and adds a component listener
     */
    public AdvancedDrawPanel() {
        // initalization of drawing objects
        rbLine = new RubberbandLine();
        rbRect = new RubberbandRectangle();
        rbEllipse = new RubberbandEllipse();
        drwPencil = new DrawPencil();
        drwAirBrush = new DrawAirBrush();

        // checks for resizing of screen
        addComponentListener(new ComponentAdapter() {
            public void componentResized(ComponentEvent event) {
                imageBuffer.update();
                drawLines();
            }
        });

    }

    /**
     * sets the time difference needed to synchronize the creation time of all objects
     */
    abstract protected void setTimeDifference(long time);

    /**
     * retrieves the time difference
     */
    abstract protected long getTimeDifference();

    /**
     * restores any non-basic object
     */
    abstract protected void restoreOther(StoredObject o); //String object);

    /**
     * generates a unique object id
     */
    abstract protected String createObjectID();

    /**
     * stores an external drawing object into image storage
     *
     * @param o Object to be stored into image storage
     */
    public void saveExternalObject(StoredObject o) {

        if (o != null) {
            if (objListExternal == null)
                objListExternal = new LinkedList();
            int i = 0;
            long t1 = o.getDate();

            // insert into storage list in the order of when object was created
            for (i = 0; i < objListExternal.size(); i++) {
                long t2 = ((StoredObject) objListExternal.get(i)).getDate();
                if (t2 > t1)
                    break;
            }

            objListExternal.add(i, o);
        }
    }

    /**
     * removes all objects from image storage and clears the display
     */
    public void removeAll() {
        removeAllExternal();
        removeAllInternal();
        clear();
    }

    /**
     * removes all internal objects from image storage
     */
    public void removeAllInternal() {
        if (objList == null)
            return;
        objList.clear();
    }

    /**
     * removes all external objects from image storage
     */
    public void removeAllExternal() {
        if (objListExternal == null)
            return;
        objListExternal.clear();
    }

    /**
     * removes a specific external object from image storage
     *
     * @param o Object to be removed from image storage
     */
    public void removeExternalObject(Object o) {
        if (o == null || (o != null && (objListExternal == null || objListExternal.size() <= 0)))
            return;

        objListExternal.remove(o);
    }

    /**
     * removes a specific internal object from image storage
     *
     * @param o Object to be removed from image storage
     */
    public void removeObject(StoredObject o) {
        if (o == null || (o != null && (objList == null || objList.size() <= 0)))
            return;
        objList.remove(o);
    }

    /**
     * removes an external object from image storage based on its object ID
     *
     * @param id The unique id of the object to be removed from image storage
     */
    public void removeExternalObjectByID(String id) {
        if (objListExternal == null || objListExternal.size() <= 0 || id == null || id.equals(""))
            return;

        int size = objListExternal.size();

        // search through list and remove specified object if found
        for (int i = 0; i < size; i++) {
            if (id.equals(((StoredObject) objListExternal.get(i)).getID())) {
                objListExternal.remove(i);
                i--;
                size = objListExternal.size();
            }
        }
    }

    /**
     * stores the stored object representation of a drawing object as an internal object
     *
     * @param o Stored object representation of a drawing object
     * @see edu.idp.shared.StoredObject
     */
    protected void saveObject(StoredObject o) {
        if (o != null) {
            o.setID(createObjectID());
            if (objList == null)
                objList = new LinkedList();
            objList.addLast(o);
        }
    }

    /*
    * retrieves all stored internal objects
    * @return NetTransferObject containing a list of all stored objects
    */
    public NetTransferObject getSavedObjects() {//String getSavedObjects() {
        return getSavedObjects(0l);
    }

    /**
     * retrieves all stored internal objects created after a specified date
     *
     * @param time Earliest creation time of an object being returned
     * @return A NetTransfer object that contains a listing of the objects found
     */
    public NetTransferObject getSavedObjects(long time) {

        NetTransferObject nto = null;
        int cntr = 0;
        if (objList == null)
            return null;

        int size = objList.size();

        // calculates current server time
        long curTime = java.util.Calendar.getInstance().getTime().getTime() + getTimeDifference();

        // traverse saved objects
        for (int i = 0; i < size; i++) {
            StoredObject tmp = (StoredObject) objList.get(i);

            // verify time of creation
            if (tmp.getDate() < time || tmp.getDeath() < curTime)
                continue;

            if (nto == null)
                nto = new NetTransferObject();

            // add to saved object list
            nto.addData(tmp.toString());
        }

        return nto;
    }

    /**
     * updates the whole internal saved object list with the specified list
     *
     * @param l List of saved objects to replace the currently stored internal
     *          stored object list. <P><b>warning: it is not recommended to use this method</b>
     */
    protected void setSavedObjects(LinkedList l) {
        objList = l;
    }

    /**
     * Returns the topmost drawing object containing the specified point
     *
     * @param p point being specified for the search
     * @return a <code>StoredObject</code> representation of the object found at the specified
     *         location, otherwise NULL is returned
     */
    public StoredObject findObjectAt(Point p) {

        if (objList == null || objList.size() <= 0)
            return null;

        StoredObject retObj = null;

        // traverse saved object list
        for (int i = 0; i < objList.size(); i++) {

            StoredObject so = (StoredObject) objList.get(i);
            Rectangle bounds = so.getBounds();

            if (bounds == null)
                continue;

            // make sure the height or width of the objects' border is never less than 4
            if (bounds.width < 4) {
                bounds.x -= (4 - bounds.width) / 2;
                bounds.width = 4;
            }
            if (bounds.height < 4) {
                bounds.y -= (4 - bounds.height) / 2;
                bounds.height = 4;
            }

            // check to see if point is within boundary area
            if (bounds != null && so.getType() != StoredObject.ANIMATED && bounds.contains(p))
                retObj = so;
        }

        return retObj;
    }

    /**
     * redraws all internal objects to the screen from a specified index in the
     * saved object list
     *
     * @param index index into the internal stored objects that specifies where to
     *              begin to restore the objects
     */
    protected void restoreAllInternal(int index) {
        int size = objList.size();

        // go through internal stored object list
        for (int i = index; i < size; i++) {
            StoredObject v = (StoredObject) objList.get(i);

            int type = v.getType();

            Storable rest = null;

            // check for basic type
            if (StoredObject.FREEHAND == type) {
                rest = (Storable) drwPencil;
            }
            else if (StoredObject.LINE == type) {
                rest = (Storable) rbLine;
            }
            else if (StoredObject.RECTANGLE == type) {
                rest = (Storable) rbRect;
            }
            else if (StoredObject.ELLIPSE == type) {
                rest = (Storable) rbEllipse;
            }
            else if (StoredObject.AIRBRUSH == type) {
                rest = (Storable) drwAirBrush;
            }
            else
                continue;

            // setup tool to draw to graphic components
            Drawable drw = (Drawable) rest;
            drw.setComponent(this);
            drw.setGraphic(imageBuffer.getBackgroundGraphics());

            // restore/redraw object
            try {
                rest.restore(v);
            }
            catch (Exception ex) {
                System.out.print("in restore");
                ex.printStackTrace();
            }

        }
    }

    /**
     * restores all external objects to the screen starting from the specified
     * object list index
     *
     * @param index index into the internal stored objects that specifies where to
     *              begin to restore the objects
     */
    protected void restoreAllExternal(int index) {
        int size = objListExternal.size();

        // go through external stored object list
        for (int i = index; i < size; i++) {
            StoredObject v = (StoredObject) objListExternal.get(i);

            int type = v.getType();
            Storable rest = null;

            // check for basic drawing object type
            if (StoredObject.FREEHAND == type) {
                rest = (Storable) drwPencil;
            }
            else if (StoredObject.LINE == type) {
                rest = (Storable) rbLine;
            }
            else if (StoredObject.RECTANGLE == type) {
                rest = (Storable) rbRect;
            }
            else if (StoredObject.ELLIPSE == type) {
                rest = (Storable) rbEllipse;
            }
            else if (StoredObject.AIRBRUSH == type) {
                rest = (Storable) drwAirBrush;
            }
            else {
                // restore unknown or advanced objects
                restoreOther(v);

                size = objListExternal.size();
                i--;
                continue;
            }

            // initialize object for drawing to a graphical object
            Drawable drw = (Drawable) rest;
            drw.setComponent(this);
            drw.setGraphic(imageBuffer.getBackgroundGraphics());

            // restore object
            try {
                rest.restore(v);
            }
            catch (Exception ex) {
                System.out.print("in restore ");
                ex.printStackTrace();
            }

        }
    }

    /**
     * restores all displayable drawing objects on the screen
     */
    public void restoreAll() {
        restoreAll(null);
    }

    /**
     * restores all displayable drawing objects within the specified bounds in the
     * order of oldest object to newest object
     *
     * @param bounds a boundary area specifying the area of the screen to be restored
     */
    public void restoreAll(Rectangle bounds) {

        clear(bounds);

        // if there are only internal objects to restore call restoreAllInternal
        // if there are only external objects to restore call restoreAllExternal
        if (objList == null || objList.size() <= 0) {
            if (objListExternal == null || objListExternal.size() <= 0)
                return;
            else
                restoreAllExternal(0);
        }
        else if (objListExternal == null || objListExternal.size() <= 0)
            restoreAllInternal(0);
        else {
            // restore mixed internal/external objects
            int sizeInternal = objList.size();
            int sizeExternal = objListExternal.size();
            int i = 0;
            int j = 0;

            // traverse through internal (local) stored objects
            for (i = 0; i < sizeInternal; i++) {

                // check to see if there are any external (non-local) objects left to restore
                if (j >= sizeExternal) {
                    restoreAllInternal(i);
                    break;
                }

                // grab objects from list
                StoredObject obj1 = (StoredObject) objList.get(i);
                StoredObject obj2 = (StoredObject) objListExternal.get(j);

                StoredObject objRestore = null;

                // restore objects within the order that they were created
                if (obj1.getDate() <= obj2.getDate())
                    objRestore = obj1;
                else {
                    i--;
                    j++;
                    objRestore = obj2;
                }

                int type = objRestore.getType();
                Storable rest = null;

                // check for basic drawing object type
                if (StoredObject.FREEHAND == type) {
                    rest = (Storable) drwPencil;
                }
                else if (StoredObject.LINE == type) {
                    rest = (Storable) rbLine;
                }
                else if (StoredObject.RECTANGLE == type) {
                    rest = (Storable) rbRect;
                }
                else if (StoredObject.ELLIPSE == type) {
                    rest = (Storable) rbEllipse;
                }
                else if (StoredObject.AIRBRUSH == type) {
                    rest = (Storable) drwAirBrush;
                }
                else {
                    if (objRestore == obj2) {
                        // if we are are restoring an external object and it is unknown/advanced restore it
                        restoreOther(obj2);
                        j--;
                        sizeExternal = objListExternal.size();
                    }
                    continue;
                }

                // initialize object for drawing to a graphical component
                Drawable drw = (Drawable) rest;
                drw.setComponent(this);
                Graphics graph = imageBuffer.getBackgroundGraphics();

                // set clipping boundary
                if (bounds != null) {
                    graph.setClip(bounds.x, bounds.y, bounds.width, bounds.height);
                }

                // initialize graphics
                drw.setGraphic(graph);

                // restore drawing object
                try {
                    rest.restore(objRestore);
                }
                catch (Exception ex) {
                    System.out.print("in restore ");
                    ex.printStackTrace();
                }

            }

            if (j < sizeExternal)
                restoreAllExternal(j);
        }

        // refresh display
        imageBuffer.blitBackgroundToWorkplace(bounds);
        imageBuffer.blitWorkplaceToScreen(bounds);

    }

    /**
     * A method that is called on the completion of a tool action.  It signals the final storage
     * and display of the newly created object
     *
     * @param ob a drawing object created by the current drawing tool
     */
    public void toolEnded(Object ob) {
        drawShape(ob);
        if (ob instanceof Storable) {
            Storable s = (Storable) ob;
            saveObject(s.store());
        }
    }

    /**
     * sets the current drawing tool to the line drawing tool
     */
    public void drawLines() {
        rbLine.setTimeDifference(this.getTimeDifference());
        setTool(rbLine);
    }

    /**
     * sets the current drawing tool to the rectangle drawing tool
     */
    public void drawRectangles() {
        rbRect.setTimeDifference(this.getTimeDifference());
        setTool(rbRect);
    }

    /**
     * sets the current drawing tool to the elliptical drawing tool
     */
    public void drawEllipses() {
        rbEllipse.setTimeDifference(this.getTimeDifference());
        setTool(rbEllipse);
    }

    /**
     * sets the current drawing tool to the pencil drawing tool
     */
    public void drawPencil() {
        drwPencil.setTimeDifference(this.getTimeDifference());
        setTool(drwPencil);
    }

    /**
     * sets the current drawing tool to the air brush drawing tool
     */
    public void drawAirbrush() {
        drwAirBrush.setTimeDifference(this.getTimeDifference());
        setTool(drwAirBrush);
    }

    /**
     * Turns off all drawing tools by setting the current drawing tool to a NULL state
     */
    public void drawNone() {
        setTool(null);
    }

    /**
     * saves the current drawing tool and current drawing color
     */
    public void saveState() {
        savedState = curDraw;
        savedColor = getColor();
    }

    /**
     * restores a previosly saved drawing tool and drawing color
     */
    public void restoreState() {
        setTool(savedState);
        setColor(savedColor);
    }

    /**
     * sets the current drawing tool.  if the tool is not a NULL tool
     * the tool is initialized with the <code>Graphic</code> object of
     * the double-buffered image
     *
     * @param d the <code>Drawable</code> object being set up as the current drawing tool
     */
    public void setTool(Drawable d) {
        if (d == null)
            super.setTool(d);
        else {
            if (d.getGraphic() == null)
                d.setGraphic(imageBuffer.getBackgroundGraphics());
            d.setColor(getColor());
            super.setTool(d);
            curDraw = d;
        }
    }

    /**
     * sets the current drawing color
     *
     * @param color the color to which the current color is being set
     */
    public void setColor(Color color) {
        this.color = color;
        if (curDraw != null)
            curDraw.setColor(color);
    }

    /**
     * retrieves the current drawing color
     *
     * @return the current drawing color
     */
    public Color getColor() {
        return color;
    }

    /**
     * sets the fillable option (if there is one) of the current drawing tool
     * <p> useful for filling in shapes, etc...
     *
     * @param b use <b>true</b> to fill in and <b>false</b> to not fill in
     */
    public void setFill(boolean b) {
        fill = b;
        objFill(b);
    }

    /**
     * returns whether or not the fillable option is set for the current drawing tool
     */
    public boolean getFill() {
        return fill;
    }

    /**
     * sets the draw line option (if there is one) of the current drawing tool
     * <p> useful for drawing outlines, etc...
     *
     * @param b use <b>true</b> to draw an outline and <b>false</b> to not use an outline
     */
    public void setLine(boolean b) {
        line = b;
        objLine(b);
    }

    /**
     * returns whether or not the draw line option is set for the current drawing tool
     */
    public boolean getLine() {
        return line;
    }

    /**
     * sets the drawing size (if available) for the current drawing tool
     * <p>useful for setting the width of lines, etc...
     *
     * @param size the width of the line/border/etc being set
     */
    public void setDrawSize(int size) {
        if (this.curDraw instanceof Sizeable)
            ((Sizeable) curDraw).setSize(size);
    }

    /**
     * sets the fillable option of the current drawing tool
     *
     * @param b use <b>true</b> to fill in and <b>false</b> to not fill in
     */
    protected void objFill(boolean b) {
        if (curDraw instanceof Fillable) {
            Fillable f = (Fillable) curDraw;
            f.setFill(b);
        }
    }

    /**
     * sets the line option of the current drawing tool
     *
     * @param b use <b>true</b> to draw an outline and <b>false</b> to not use an outline
     */
    protected void objLine(boolean b) {
        if (curDraw instanceof Fillable) {
            Fillable f = (Fillable) curDraw;
            f.setLine(b);
        }
    }

    /**
     * if the object passed in is a drawable object, bring the objects image from the background
     * to the workplace buffer
     *
     * @param ob object to draw to the workplace buffer
     */
    protected void drawShape(Object ob) {
        if (ob instanceof Drawable) {
            Drawable d = (Drawable) ob;
            Rectangle r = d.getBounds();
            blitBackgroundToWorkplace(new Rectangle(r.x - 1, r.y - 1, r.width + 2, r.height + 2));
        }
    }

    /**
     * repaint the display
     *
     * @param g <code>Graphics</code> object for the current display
     */
    public void repaint(Graphics g) {
        paint(g);
    }

    /**
     * draws a line based on a <code>Rubberband</code> anchor and end points and displays
     * it to screen.
     *
     * @depreciated this code is no longer used.  The <code>Rubberband</code> now takes care
     * of its own drawing of lines/rectangles/etc..
     */
    protected void drawLine(Rubberband rb, Graphics g, Graphics gb) {
        Point anchor = rb.getAnchor(), end = rb.getEnd();

        gb.drawLine(anchor.x, anchor.y, end.x, end.y);
        g.drawLine(anchor.x, anchor.y, end.x, end.y);

        Rectangle r = rb.getBounds();
        blitBackgroundToWorkplace(new Rectangle(r.x - 1, r.y - 1, r.width + 2, r.height + 2));
    }

    /**
     * draws a rectangle based on a <code>Rubberband</code> anchor and end points and displays
     * it to screen.
     *
     * @depreciated this code is no longer used.  The <code>Rubberband</code> now takes care
     * of its own drawing of lines/rectangles/etc..
     */
    protected void drawRectangle(Rubberband rb, Graphics g, Graphics gb) {

        Rectangle r = rb.getBounds();

        // determine if rectangle needs to be filled
        if (fill) {
            // determine if an outline is to be drawn around the shape
            if (line) {
                gb.fillRect(r.x + 1, r.y + 1, r.width - 1, r.height - 1);
                g.fillRect(r.x + 1, r.y + 1, r.width - 1, r.height - 1);
                g.setColor(Color.black);
                gb.setColor(Color.black);
                g.drawRect(r.x, r.y, r.width, r.height);
                gb.drawRect(r.x, r.y, r.width, r.height);
            }
            else {
                gb.fillRect(r.x, r.y, r.width + 1, r.height + 1);
                g.fillRect(r.x, r.y, r.width + 1, r.height + 1);
            }
        }
        else {
            gb.drawRect(r.x, r.y, r.width, r.height);
            g.drawRect(r.x, r.y, r.width, r.height);
        }
        blitBackgroundToWorkplace(new Rectangle(r.x - 1, r.y - 1, r.width + 2, r.height + 2));
    }

    /**
     * draws an ellipse based on a <code>Rubberband</code> anchor and end points and displays
     * it to screen.
     *
     * @depreciated this code is no longer used.  The <code>Rubberband</code> now takes care
     * of its own drawing of lines/rectangles/etc..
     */
    protected void drawEllipse(Rubberband rb, Graphics g, Graphics gb) {
        Rectangle r = rb.getBounds();

        // determine if ellipse needs to be filled
        if (fill) {
            // determine if an outline is to be drawn around the shape
            if (line) {
                gb.fillArc(r.x + 1, r.y + 1, r.width - 1, r.height - 1, 0, 360);
                g.fillArc(r.x + 1, r.y + 1, r.width - 1, r.height - 1, 0, 360);
                g.setColor(Color.black);
                gb.setColor(Color.black);
                g.drawArc(r.x, r.y, r.width, r.height, 0, 360);
                gb.drawArc(r.x, r.y, r.width, r.height, 0, 360);
            }
            else {
                gb.fillArc(r.x, r.y, r.width + 1, r.height + 1, 0, 360);
                g.fillArc(r.x, r.y, r.width + 1, r.height + 1, 0, 360);
            }
        }
        else {
            gb.drawArc(r.x, r.y, r.width, r.height, 0, 360);
            g.drawArc(r.x, r.y, r.width, r.height, 0, 360);
        }
        blitBackgroundToWorkplace(new Rectangle(r.x - 1, r.y - 1, r.width + 2, r.height + 2));
    }

    /**
     * clears the current display area
     */
    public void clear() {
        clear(null);
    }

    /**
     * clears a specific area of the screen
     *
     * @param bounds a specified area of the screen to be cleared
     */
    public void clear(Rectangle bounds) {
        Graphics g = this.getGraphics();
        if (g == null)
            return;

        Color c = this.getBackground();

        if (bounds == null) {
            // if the bounds is null, clear the whole screen
            Dimension size = this.getSize();
            g.setColor(c);
            g.fillRect(0, 0, size.width, size.height);
            g = imageBuffer.getBackgroundGraphics();
            g.setColor(c);
            g.fillRect(0, 0, size.width, size.height);
            g.dispose();
            g = imageBuffer.getWorkplaceGraphics();
            g.setColor(c);
            g.fillRect(0, 0, size.width, size.height);
            g.dispose();
        }
        else {
            // fill in the specified area with the background color, in effect clearing the area
            g.setColor(c);
            g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
            g = imageBuffer.getBackgroundGraphics();
            g.setColor(c);
            g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
            g.dispose();
            g = imageBuffer.getWorkplaceGraphics();
            g.setColor(c);
            g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
            g.dispose();
        }
    }

    /**
     * refreshes the screen
     *
     * @param g the <code>Graphics</code> object of the screen
     */
    public void update(Graphics g) {
        paint(g);
    }

    /**
     * draws the current image to the screen in case the screen needs to be refreshed
     *
     * @param g the graphics object of the screen
     */
    public void paint(Graphics g) {
        Rectangle clip = g.getClipBounds();

        // check to see if any of the display screen had been covered (damaged)
        if (windowDamaged(g)) {
            imageBuffer.blitWorkplaceToScreen(clip);
        }
        else {
            // redisplay the whole image
            Graphics wpg = imageBuffer.getWorkplaceGraphics();
            Dimension size = getSize();

            paintBackground();
            wpg.setClip(0, 0, size.width, size.height);
            super.paint(wpg);
            compRedraw = true;
            imageBuffer.blitWorkplaceToScreen();
            wpg.dispose();
        }
    }

    /**
     * displays the contents of the current workplace buffer to the screen
     */
    public void blitWorkplaceToScreen() {
        imageBuffer.blitWorkplaceToScreen();
    }

    /**
     * moves the contents of the background buffer to the workplace buffer overwriting
     * the contents of the workplace buffer
     */
    public void blitBackgroundToWorkplace() {
        imageBuffer.blitBackgroundToWorkplace();
    }

    /**
     * displays the contents specified area of the current workplace bufer to the screen
     */
    public void blitWorkplaceToScreen(Rectangle clip) {
        imageBuffer.blitWorkplaceToScreen(clip);
    }

    /**
     * moves the contents of the speciified area of the background buffer to the workplace buffer
     * overwriting the contents of the workplace buffer in the same area
     */
    public void blitBackgroundToWorkplace(Rectangle clip) {
        imageBuffer.blitBackgroundToWorkplace(clip);
    }

    /**
     * draws all components that are contained within the area specified in the drawing area
     * to the workplace buffer
     *
     * @param clip   the clipping area in which to draw the components
     * @param update whether or not to update the screen with the workplace buffer's contents
     */
    public void paintComponents(Rectangle clip, boolean update) {

        Graphics wpg = imageBuffer.getWorkplaceGraphics();

        // set the clipping area of the workplace graphic
        wpg.setClip(clip);
        super.paint(wpg);

        compRedraw = true;

        // update the screen
        if (update) {
            imageBuffer.blitWorkplaceToScreen(clip);
        }

        wpg.dispose();
    }

    /**
     * displays the specified component onto the screen
     *
     * @param comp the component to be displayed to the screenh
     */
    public void paintComponent(Component comp) {
        paintComponent(comp, true);
    }

    /**
     * paints the specified component to the workplace buffer and if update
     * is true, to the screen as well
     *
     * @param comp   the component to paint to the workplace buffer/screen
     * @param update if true, update the display with the contents of the
     *               workplace buffer, else don't update the display
     */
    public void paintComponent(Component comp, boolean update) {
        Graphics wpg = imageBuffer.getWorkplaceGraphics();
        Rectangle bounds = comp.getBounds();
        Graphics compGraphics;

        compRedraw = true;

        // create a graphics object
        compGraphics = wpg.create(bounds.x, bounds.y,
                                  bounds.width, bounds.height);

        // paint the component to the created graphics object
        comp.paint(compGraphics);

        // display the workplace buffer to the screen
        if (update)
            imageBuffer.blitWorkplaceToScreen(bounds);

        wpg.dispose();
    }

    /**
     * erases the specified object from the screen
     *
     * @param comp the object to be removed from the screen
     */
    public void eraseComponent(Component comp) {
        eraseComponent(comp, true);
    }

    /**
     * erases the specified object from the workplace buffer and if update is true
     * the component is also removed from the display as well
     *
     * @param comp   the component to be removed
     * @param update if true, remove the object from the display and the workplace
     *               buffer, else only remove from the workplace buffer
     */
    public void eraseComponent(Component comp, boolean update) {
        // retrieve the bounds of the component being removed
        Rectangle bounds = comp.getBounds();

        compRedraw = true;
        // removes the component from the workplace by overwriting its boundary area
        imageBuffer.blitBackgroundToWorkplace(bounds);
        paintOverlappingComponents(comp);

        // update display if necessary
        if (update)
            imageBuffer.blitWorkplaceToScreen(bounds);
    }

    /**
     * moves the specified component to the specified location
     *
     * @param comp   the component that is being moved
     * @param newLoc the location to where the component is being moved
     */
    public void moveComponent(Component comp, Point newLoc) {
        Rectangle oldBounds = comp.getBounds();

        eraseComponent(comp, false);  // erase - no screen update
        comp.setLocation(newLoc);     // move component
        paintComponent(comp, false);  // paint comp - no update

        // update screen
        imageBuffer.blitWorkplaceToScreen(
                oldBounds.union(comp.getBounds()));
    }

    /**
     * returns whether or not the graphic object has been <i>damaged</i>.  where damaged
     * means <b>part</b> of the object has been covered by another windows, toolbar, etc and needs
     * to be refreshed.
     */
    protected boolean windowDamaged(Graphics g) {
        Rectangle clip = g.getClipBounds();
        Dimension size = getSize();

        return ((clip.x != 0 || clip.y != 0) ||
                (clip.width < size.width ||
                        clip.height < size.height));
    }

    /**
     * paints all components that overlap the specified components on the display
     *
     * @param comp the component used to in specifying the overlapping compoments
     */
    protected void paintOverlappingComponents(Component comp) {
        Graphics wpg = imageBuffer.getWorkplaceGraphics();
        Rectangle bounds = comp.getBounds();

        wpg.setClip(bounds); // set offscreen clip to bounds
        comp.setVisible(false);  // so comp won't be drawn below
        super.paint(wpg);    // draw all but comp
        comp.setVisible(true);   // reset comp visibility
        wpg.dispose();
    }

    /**
     * paints the contents of the backgound buffer to the workplace buffer
     */
    protected void paintBackground() {
        paintBackground((Rectangle) null);
    }

    /**
     * paints the contents fo the specified area of the background buffer to the
     * workplace buffer
     *
     * @param clip the clipping boundary of the area in the background buffer to
     *             move to the workplace buffer
     */
    protected void paintBackground(Rectangle clip) {
        Graphics g = imageBuffer.getBackgroundGraphics();
        if (clip != null) {
            g.setClip(clip);
        }
        //paintBackground(g);
        imageBuffer.blitBackgroundToWorkplace();
        g.dispose();
    }

    /**
     * Internal class that maintains the double buffers used in the <code>AdvancedDrawPanel</code> class
     *
     * @author David Geary
     * @version 1.0, January, 1997
     */
    class OffscreenBuffer {
        private Image workplaceBuffer, backgroundBuffer;
        private Dimension offscreenSize;

        /**
         * returns the workplace buffer
         */
        public Image getWorkplaceBuffer() {
            return workplaceBuffer;
        }

        /**
         * returns the background buffer
         */
        public Image getBackground() {
            return backgroundBuffer;
        }

        /**
         * returns the <code>Graphics</code> object of the workplace buffer
         */
        public Graphics getWorkplaceGraphics() {
            return workplaceBuffer.getGraphics();
        }

        /**
         * returns the <code>Graphics</code> object of the background buffer
         */
        public Graphics getBackgroundGraphics() {
            return backgroundBuffer.getGraphics();
        }

        /**
         * displays the workplace buffer to the screen
         */
        public void blitWorkplaceToScreen() {
            blitWorkplaceToScreen(null);
        }

        /**
         * copies the contents of the background buffer to the workplace buffer
         */
        public void blitBackgroundToWorkplace() {
            blitBackgroundToWorkplace(null);
        }

        /**
         * displays the specified area of the workplace buffer to the screen
         */
        public void blitWorkplaceToScreen(Rectangle clip) {
            Graphics screenGraphics = getGraphics();

            if (screenGraphics != null) {
                if (clip != null) {
                    screenGraphics.setClip(clip);
                }

                screenGraphics.drawImage(workplaceBuffer, 0, 0, AdvancedDrawPanel.this);
                screenGraphics.dispose();
            }
        }

        /**
         * copies the specified area of the background buffer to the workplace buffer
         */
        public void blitBackgroundToWorkplace(Rectangle clip) {
            Graphics wpg = getWorkplaceGraphics();

            if (clip != null) {
                wpg.setClip(clip);
            }
            wpg.drawImage(backgroundBuffer, 0, 0,
                          AdvancedDrawPanel.this);

            wpg.dispose();
        }

        /**
         * if the background and workplace buffers need to be recreated,
         * they are created
         */
        void update() {
            if (needNewOffscreenBuffer()) {
                createBuffers();
            }
        }

        /**
         * checks to see if the offscreen buffers need to be created.  They will need to
         * be created if the display size changes or the buffers have not been initialized
         */
        private boolean needNewOffscreenBuffer() {
            Dimension size = getSize();
            return (workplaceBuffer == null ||
                    size.width != offscreenSize.width ||
                    size.height != offscreenSize.height);
        }

        /**
         * creates the workplace and background buffers with the same dimension as the current
         * display area
         */
        private void createBuffers() {
            offscreenSize = getSize();
            workplaceBuffer = createOffscreenImage(offscreenSize);
            backgroundBuffer = createOffscreenImage(offscreenSize);

            Dimension size = AdvancedDrawPanel.this.getSize();
            Color c = AdvancedDrawPanel.this.getBackground();

            // makes sure the buffers have the same base color as the display
            Graphics g = getWorkplaceGraphics();
            g.setColor(AdvancedDrawPanel.this.getBackground());
            g.fillRect(0, 0, size.width, size.height);
            g.dispose();

            g = getBackgroundGraphics();
            g.setColor(AdvancedDrawPanel.this.getBackground());
            g.fillRect(0, 0, size.width, size.height);
            g.dispose();
        }

        /**
         * creates an offscreen image of the specified dimension
         */
        private Image createOffscreenImage(Dimension size) {
            Image image = createImage(size.width, size.height);
            Util.waitForImage(AdvancedDrawPanel.this, image);
            return image;
        }
    }

}















